package com.otitan.la.forest.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.otitan.auth.framework.basepro.common.AuthCommon;
import com.otitan.la.forest.system.config.code.FjsResponseCode;
import com.otitan.la.forest.system.constant.BaseParamConstant;
import com.otitan.la.forest.system.constant.DicTypeEnum;
import com.otitan.la.forest.system.constant.StaticConstant;
import com.otitan.la.forest.system.dto.LoginUserDTO;
import com.otitan.la.forest.system.dto.UserDTO;
import com.otitan.la.forest.system.dto.UserResetDTO;
import com.otitan.la.forest.system.entity.*;
import com.otitan.la.forest.system.exception.code.LaBaseResponseCode;
import com.otitan.la.forest.system.mapper.MenuMapper;
import com.otitan.la.forest.system.mapper.PermissionMapper;
import com.otitan.la.forest.system.mapper.RoleMapper;
import com.otitan.la.forest.system.mapper.UserMapper;
import com.otitan.la.forest.system.service.*;
import com.otitan.la.forest.system.util.AutoCommonUtil;
import com.otitan.la.forest.system.vo.*;
import com.otitan.webapp.framework.basepro.exception.code.BaseResponseCode;
import com.otitan.webapp.framework.basepro.model.DataResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {

    @Autowired
    private AuthCommon authCommon;
    @Resource
    private UserMapper userMapper;
    @Resource
    private EditPwdRecordService editPwdRecordService;
    @Resource
    private BaseParamService baseParamService;
    @Autowired
    private UserOrgService userOrgService;
    @Autowired
    private UserRoleService userRoleService;
    @Resource
    private RoleMapper roleMapper;
    @Resource
    private MenuMapper menuMapper;
    @Resource
    private OrganizationService organizationService;
    @Autowired
    private MenuService menuService;
    @Autowired
    private ApplicationService applicationService;
    @Resource
    private PermissionMapper permissionMapper;

    @Autowired
    private DicService dicService;
    @Autowired
    private LogService logService;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${redis.key-prefix}")
    private String redisKeyPrefix;
    @Value("${project.token.time-out}")
    private String tokenTimeOut;

    @Override
    public DataResult add(UserEntity user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return DataResult.fail(bindingResult.getFieldError().getDefaultMessage());
        }
        // 用户名不能重复
        LambdaQueryWrapper<UserEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(UserEntity::getUserName, user.getUserName());
        int count = this.count(queryWrapper);
        if (count > 0) {
            return DataResult.fail("用户名已存在");
        }
        // 设置初始密码
        String salt = StaticConstant.SALT;
        String defaultPassword = baseParamService.findOne(BaseParamConstant.DEFAULT_PASSWORD).getParamValue();
        String password = AutoCommonUtil.encryption(salt, defaultPassword);
        user.setPassword(password);
        boolean isSuccess = this.save(user);
        return isSuccess ? DataResult.success() : DataResult.fail(BaseResponseCode.DATA_NOT_EXIST.getMsg());
    }

    @Override
    @Transactional
    public DataResult batchDelete(List<String> ids) {
        // 删除用户组织机构关系表
        LambdaQueryWrapper<UserOrgEntity> userOrgWrapper = Wrappers.lambdaQuery();
        userOrgWrapper.in(UserOrgEntity::getUserId, ids);
        userOrgService.remove(userOrgWrapper);
        // 删除用户角色关系表
        LambdaQueryWrapper<UserRoleEntity> userRoleWrapper = Wrappers.lambdaQuery();
        userRoleWrapper.in(UserRoleEntity::getUserId, ids);
        userRoleService.remove(userRoleWrapper);
        // 删除用户
        boolean isSuccess = this.removeByIds(ids);
        log.debug("\n>>>>>>>>> 【删除用户表】 <<<<<<<<<\n操作结果：{}，ids：{}\n>>>>>>>>> ---- <<<<<<<<<", isSuccess, ids);
        return DataResult.success();
    }

    @Override
    public DataResult editor(UserEntity user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return DataResult.fail(bindingResult.getFieldError().getDefaultMessage());
        }
        // 用户名不能重复
        LambdaQueryWrapper<UserEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(UserEntity::getUserName, user.getUserName());
        queryWrapper.ne(UserEntity::getId, user.getId());
        int count = this.count(queryWrapper);
        if (count > 0) {
            return DataResult.fail("用户名已存在");
        }
        boolean isSuccess = this.updateById(user);
        return isSuccess ? DataResult.success() : DataResult.fail(BaseResponseCode.DATA_NOT_EXIST.getMsg());
    }

    @Override
    public DataResult findPageData(long page, long size, UserDTO user) {
        Page<UserEntityVO> pagePlug = new Page<>(page, size);
        pagePlug.setRecords(this.baseMapper.findPageData(user, pagePlug));
        List<UserEntityVO> records = pagePlug.getRecords();
        records.forEach(item -> {
            String politicalLandscape = dicService.getDicName(DicTypeEnum.POLITICAL_ANDSCAPE.getValue(), item.getPoliticalLandscape());
            item.setPoliticalLandscapeName(politicalLandscape);
        });
        pagePlug.setRecords(records);
        return DataResult.success(pagePlug);
    }

    @Override
    public DataResult findOne(String id) {

        UserEntity entity = this.getById(id);
        UserEntityVO entityVO = new UserEntityVO();
        BeanUtils.copyProperties(entity, entityVO);
        String politicalLandscape = dicService.getDicName(DicTypeEnum.POLITICAL_ANDSCAPE.getValue(), entityVO.getPoliticalLandscape());
        entityVO.setPoliticalLandscapeName(politicalLandscape);

        return DataResult.success(entityVO);
    }

    /**
     * 检查用户是否禁用
     *
     * @param [userName]
     * @return void
     * @throws
     * @author jiachangsen
     * @date 2020/10/10 14:35
     */
    private String checkUserLock(String userName) {
        // 该用户登录错误次数
        Long userLoginErrorNum = baseParamService.getUserLoginErrorNum(userName);
        // 锁定次数
        Long passwordRetryNum = Long.parseLong(baseParamService.findOne(BaseParamConstant.PASSWORD_RETRY_NUM).getParamValue());
        // 锁定时间
        Long lockTime = Long.parseLong(baseParamService.findOne(BaseParamConstant.LOCK_TIME).getParamValue());
        if (passwordRetryNum <= userLoginErrorNum) {
            // 错误次数已达到最大值， 用户被锁定
            return "您连续输入错误已达到" + passwordRetryNum + "次，锁定" + lockTime + "分钟";
        }
        return null;
    }

    @Override
    public DataResult login(LoginUserDTO dto) {
        String userName = dto.getUserName();
        String password = dto.getPassword();

        // 检查用户是否禁用
        String msg = checkUserLock(userName);
        if (StrUtil.isNotEmpty(msg)) {
            return new DataResult(LaBaseResponseCode.USER_LOCK.getCode(), msg);
        }

        String salt = StaticConstant.SALT;
        String passwordM = AutoCommonUtil.encryption(salt, password);

        Map<String, String> map = new HashMap<>();
        map.put("userName", userName);
        map.put("password", passwordM);
        //查询并缓存当前用户
        UserEntity userEntity = baseMapper.findByUserNameAndPassword(map);
        if (null == userEntity) {
            // 记录用户登录错误次数
            baseParamService.addUserLoginErrorNum(userName);
            return DataResult.fail("用户名或密码错误");
        }
        // 将用户信息存储到 redisMyHandlerInterceptor
        redisTemplate.opsForValue().set(redisKeyPrefix + userEntity.getUserName(),
                JSONObject.toJSONString(userEntity),
                Long.parseLong(tokenTimeOut), TimeUnit.MILLISECONDS);
        // 开始鉴权
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
        String tokenId = "";
        LoginVO resVO = new LoginVO();

        try {
            SecurityUtils.getSubject().login(usernamePasswordToken);
            SecurityUtils.getSubject().getSession().setTimeout(Long.parseLong(tokenTimeOut));
            JSONObject user = (JSONObject) SecurityUtils.getSubject().getPrincipal();
            if (user == null) {
                // 记录用户登录错误次数
                baseParamService.addUserLoginErrorNum(userName);
                return DataResult.fail("用户名或密码错误");
            }
            Subject subject = SecurityUtils.getSubject();
            tokenId = String.valueOf(subject.getSession().getId());
            SecurityUtils.getSubject().getSession().setAttribute(dto.getUserName(), user);
            // 用户权限加入缓存
            setAuth(tokenId, user);
            // 判断是否需要修改密码
            resVO.setChangePassword(ifChangePassword(user.get("id").toString()));
            resVO.setTokenId(tokenId);
            resVO.setUser(JSONObject.parseObject(user.toJSONString(), UserEntity.class));
        } catch (AuthenticationException e) {
            log.error("密码错误！");
            // 记录用户登录错误次数
            baseParamService.addUserLoginErrorNum(userName);
            return DataResult.fail("用户名或密码错误");
        }
        // 删除用户登录错误次数记录
        baseParamService.removeUserLoginError(userName);

        // 添加系统日志
        LogEntity logUser = new LogEntity();
        logUser.setAction("login_in");
        logUser.setAppCode("system");
        logService.setSystemLogger(logUser, tokenId);
        return DataResult.success(resVO);
    }

    /**
     * 判断是否需要修改密码
     *
     * @param [dto]
     * @return boolean
     * @throws
     * @author jiachangsen
     * @date 2020/10/09 14:21
     */
    private boolean ifChangePassword(String userId) {
        LambdaQueryWrapper<EditPwdRecordEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(EditPwdRecordEntity::getUserId, userId);
        int count = editPwdRecordService.count(queryWrapper);
        return count == 0;
    }

    @Override
    public DataResult getLoginUserApp() {
        JSONObject user = authCommon.getLoginUserInfo();
        List<ApplicationEntity> applicationInfo = applicationService.findUserApplicationInfo(user.get("id").toString());
        return DataResult.success(applicationInfo);
    }

    @Override
    public DataResult getLoginUserAuth(HttpServletRequest request, String appCode) {
        JSONObject user = authCommon.getLoginUserInfo();
        String userId = user.get("id").toString();
        // 根据用户ID、应用ID  查询菜单
        List<MenuTreeVO> menuInfo = menuMapper.findUserMenuInfo(userId, appCode);
        if (CollUtil.isNotEmpty(menuInfo)) {
            List<MenuTreeVO> userMenuTree = menuService.formatData(menuInfo, "0");

            Set<String> menuCodes = menuInfo.stream().map(MenuTreeVO::getMenuCode).collect(Collectors.toSet());
            LoginUserAuthVO vo = new LoginUserAuthVO();
            vo.setTreeMenus(userMenuTree);
            if (CollUtil.isNotEmpty(menuCodes)) {
                // 根据用户ID、应用ID  查询按钮
                List<PermissionVO> permissionList = permissionMapper.findPermission(userId, menuCodes);
                Map<String, List<PermissionVO>> collect = permissionList.stream()
                        .collect(Collectors.groupingBy(PermissionVO::getMenuCode));
                vo.setPermissions(collect);
            }
            return DataResult.success(vo);
        } else {
            return new DataResult(FjsResponseCode.NOT_MENU_POWER.getCode(), FjsResponseCode.NOT_MENU_POWER.getMsg());
        }
    }

    /**
     * 获取用户菜单
     *
     * @param
     * @return void
     * @throws
     * @author jiachangsen
     * @date 2020/09/18 15:27
     */
    private void getUserMenu(LoginVO vo, String tokenId, String userId) {
        // 根据用户ID查询查询应用
        List<ApplicationEntity> appList = applicationService.findUserApplicationInfo(userId);
        vo.setTokenId(tokenId);
    }


    /**
     * 用户权限加入缓存
     *
     * @param
     * @return void
     * @throws
     * @author jiachangsen
     * @date 2020/09/18 15:21
     */
    private void setAuth(String tokenId, JSONObject user) {
        String id = user.get("id").toString();
        BaseParamEntity userLoginErrorNum = baseParamService.findOne(BaseParamConstant.ONLY_ONE_LOGIN);
        String value = userLoginErrorNum.getParamValue();

        // 删除该用户之前的鉴权数据
        if ("YES".equalsIgnoreCase(value)) {
            String otherToken = redisTemplate.opsForValue().get(redisKeyPrefix + StaticConstant.UNIQUE_LOGIN + id);
            if (StrUtil.isNotEmpty(otherToken)) {
                redisTemplate.delete(redisKeyPrefix + otherToken);
            }
        }
        // 查询用户权限，缓存Redis
        List<String> roles = roleMapper.findRoleByUserId(id);
        List<String> menus = menuMapper.findMenuByUserId(id);
        List<Map> permissions = permissionMapper.findPermissionByUserId(id);
        List<OrganizationEntity> orgInfo = organizationService.findOrgInfoByUserId(id);
        Map<String, Object> redisUserMap = new HashMap();
        redisUserMap.put("user", user);
        redisUserMap.put("roles", roles);
        redisUserMap.put("menus", menus);
        redisUserMap.put("org", orgInfo);
        redisUserMap.put("permissions", permissions);
        redisTemplate.opsForValue().set(redisKeyPrefix + tokenId,
                JSONObject.toJSONString(redisUserMap),
                Long.parseLong(tokenTimeOut), TimeUnit.MILLISECONDS);
        redisUserMap.put("permissions", permissions);

        // 缓存限制同一用户多处登录信息
        if ("YES".equalsIgnoreCase(value)) {
            redisTemplate.opsForValue().set(redisKeyPrefix + StaticConstant.UNIQUE_LOGIN + id,
                    tokenId,
                    Long.parseLong(tokenTimeOut), TimeUnit.MILLISECONDS);
        }

    }

    /**
     * 重置密码
     *
     * @param id
     * @return com.otitan.webapp.framework.basepro.model.DataResult
     * @throws
     * @author Sunke
     * @date 2020/9/27 0027 09:48
     */
    @Transactional
    @Override
    public DataResult resetPwd(String id) {
        String salt = StaticConstant.SALT;
        String defaultPassword = baseParamService.findOne(BaseParamConstant.DEFAULT_PASSWORD).getParamValue();
        String newPassword = AutoCommonUtil.encryption(salt, defaultPassword);
        UserEntity user = this.getById(id);
        user.setPassword(newPassword);
        boolean isSuccess = this.updateById(user);

        // 删除修改密码记录
        LambdaQueryWrapper<EditPwdRecordEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(EditPwdRecordEntity::getUserId, id);
        editPwdRecordService.remove(queryWrapper);

        return isSuccess ? DataResult.success() : DataResult.fail(BaseResponseCode.DATA_NOT_EXIST.getMsg());
    }

    /**
     * 用户修改密码
     *
     * @param userDTO
     * @return com.otitan.webapp.framework.basepro.model.DataResult
     * @throws
     * @author Sunke
     * @date 2020/9/27 0027 10:01
     */
    @Transactional
    @Override
    public DataResult updatePwd(UserResetDTO userDTO, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return DataResult.fail(bindingResult.getFieldError().getDefaultMessage());
        }
        LambdaQueryWrapper<UserEntity> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(UserEntity::getUserName, userDTO.getUserName());
        UserEntity user = userMapper.selectOne(queryWrapper);
        String salt = StaticConstant.SALT;
        String oldPwd = AutoCommonUtil.encryption(salt, userDTO.getOldPassword());
        if (!user.getPassword().equals(oldPwd)) {
            return DataResult.fail("密码错误");
        }
        String password = AutoCommonUtil.encryption(salt, userDTO.getNewPassword());
        user.setPassword(password);
        boolean isSuccess = this.updateById(user);
        // 添加修改密码记录
        EditPwdRecordEntity editPwdRecordEntity = new EditPwdRecordEntity(user.getId(), password);
        editPwdRecordService.save(editPwdRecordEntity);
        return isSuccess ? DataResult.success() : DataResult.fail(BaseResponseCode.DATA_NOT_EXIST.getMsg());
    }

    @Override
    public DataResult<UserEntity> getLoginUserInfo() {
        JSONObject user = authCommon.getLoginUserInfo();
        JSONArray orgInfo = authCommon.getLoginOrgInfo();
        JSONArray roleCode = authCommon.getLoginRoleCode();
        user.put("role", roleCode);
        if (CollUtil.isNotEmpty(user)) {
            if (CollUtil.isNotEmpty(orgInfo)){
                user.put("org", orgInfo.get(0));
            } else {
                user.put("org", null);
            }
            return DataResult.success(user);
        }
        return new DataResult<>(BaseResponseCode.TOKEN_ERROR);
    }
}